package cryptoutil

import (
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/bccsp"
	"github.com/hyperledger/fabric/bccsp/factory"
	"github.com/hyperledger/fabric/bccsp/signer"
	cc "github.com/hyperledger/fabric/common/crypto"
	cb "github.com/hyperledger/fabric/protos/common"
	"github.com/hyperledger/fabric/protos/msp"
	"github.com/hyperledger/fabric/protos/peer"
	putils "github.com/hyperledger/fabric/protos/utils"
)

type Signer interface {
	Serialize() ([]byte, error)
	Sign(msp []byte) ([]byte, error)
	NewSignatureHeader() (*cb.SignatureHeader, error)
}

func GetSignedProposal(prop *peer.Proposal, signer Signer) (*peer.SignedProposal, error) {
	propBytes, err := proto.Marshal(prop)
	if err != nil {
		return nil, err
	}

	signature, err := signer.Sign(propBytes)
	if err != nil {
		return nil, err
	}

	return &peer.SignedProposal{ProposalBytes: propBytes, Signature: signature}, nil
}

func createSignedEnvelopeWithTLSBinding(txType cb.HeaderType, channelID string /*signer cc.LocalSigner*/, signer Signer, dataMsg proto.Message, msgVersion int32, epoch uint64, tlsCertHash []byte) (*cb.Envelope, error) {
	payloadChannelHeader := putils.MakeChannelHeader(txType, msgVersion, channelID, epoch)
	payloadChannelHeader.TlsCertHash = tlsCertHash
	var err error
	payloadSignatureHeader := &cb.SignatureHeader{}
	if signer != nil {
		payloadSignatureHeader, err = signer.NewSignatureHeader()
		if err != nil {
			return nil, err
		}
	}
	data, err := proto.Marshal(dataMsg)
	if err != nil {
		//fmt.Println("marshal error")
		return nil, err
	}

	payloadBytes := putils.MarshalOrPanic(&cb.Payload{
		Header: putils.MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader),
		Data:   data,
	})
	var sig []byte
	if signer != nil {
		sig, err = signer.Sign(payloadBytes)
		if err != nil {
			return nil, err
		}
	}

	env := &cb.Envelope{
		Payload:   payloadBytes,
		Signature: sig,
	}
	return env, nil
}

func getHashOpt(hashFamily string) (bccsp.HashOpts, error) {
	switch hashFamily {
	case bccsp.SHA2:
		return bccsp.GetHashOpt(bccsp.SHA256)
	case bccsp.SHA3:
		return bccsp.GetHashOpt(bccsp.SHA3_256)
	default:
		return nil, fmt.Errorf("not support hash option:%s", hashFamily)
	}
}

type localSigner struct {
	cert      *x509.Certificate
	prik      bccsp.Key
	bccsp     bccsp.BCCSP
	cryptoOpt *factory.FactoryOpts
	mspid     string
}

func (localSigner localSigner) getHashOpt(hashFamily string) (bccsp.HashOpts, error) {
	switch hashFamily {
	case bccsp.SHA2:
		return bccsp.GetHashOpt(bccsp.SHA256)
	case bccsp.SHA3:
		return bccsp.GetHashOpt(bccsp.SHA3_256)
	default:
		return nil, fmt.Errorf("not support hash option:%s", hashFamily)
	}
}

func (localSigner *localSigner) Serialize() ([]byte, error) {
	pblock := &pem.Block{Bytes: localSigner.cert.Raw, Type: "CERTIFICATE"}
	pemBytes := pem.EncodeToMemory(pblock)
	id := &msp.SerializedIdentity{Mspid: localSigner.mspid, IdBytes: pemBytes}
	bytes, err := proto.Marshal(id)
	if err != nil {
		return nil, err
	}
	return bytes, nil
}

func (localSigner *localSigner) newSigner() (crypto.Signer, error) {
	prk, err := localSigner.bccsp.GetKey(localSigner.prik.SKI())
	if err != nil {
		return nil, err
	}
	return signer.New(localSigner.bccsp, prk)
}

func (localSigner *localSigner) Sign(msg []byte) ([]byte, error) {
	hashOpt, err := localSigner.getHashOpt(localSigner.cryptoOpt.SwOpts.HashFamily)
	if err != nil {
		return nil, err
	}
	digest, err := localSigner.bccsp.Hash(msg, hashOpt)
	if err != nil {
		return nil, err
	}
	sign, err := localSigner.newSigner()
	if err != nil {
		return nil, err
	}
	return sign.Sign(rand.Reader, digest, nil)
}

func (localSigner *localSigner) NewSignatureHeader() (*cb.SignatureHeader, error) {
	creator, err := localSigner.Serialize()
	if err != nil {
		return nil, err
	}
	nonce, err := cc.GetRandomNonce()
	if err != nil {
		return nil, err
	}
	sh := &cb.SignatureHeader{}
	sh.Creator = creator
	sh.Nonce = nonce
	return sh, nil
}

type myCryptoSigner struct {
	opt string
	key Key
	mspID string
	cert *x509.Certificate
}

func (s *myCryptoSigner) getSigner() (crypto.Signer, error) {
	return newEcdsaSigner(s.key)
}

func (s *myCryptoSigner) Sign(msg []byte) ([]byte, error) {
	hashOpt, err := getHashOpt(bccsp.SHA2)
	if err != nil {
		return nil, err
	}
	digest, err := Hash(msg, hashOpt)
	if err != nil {
		return nil, err
	}
	mySigner, err := s.getSigner()
	if err != nil {
		return nil, err
	}
	return mySigner.Sign(rand.Reader, digest, nil)
}

func (s *myCryptoSigner) Serialize() ([]byte, error) {
	pblock := &pem.Block{Bytes: s.cert.Raw, Type: "CERTIFICATE"}
	pemBytes := pem.EncodeToMemory(pblock)
	id := &msp.SerializedIdentity{Mspid: s.mspID, IdBytes: pemBytes}
	bytes, err := proto.Marshal(id)
	if err != nil {
		return nil, err
	}
	return bytes, nil
}

func (s *myCryptoSigner) NewSignatureHeader() (*cb.SignatureHeader, error)  {
	creator, err := s.Serialize()
	if err != nil {
		return nil, err
	}
	nonce, err := cc.GetRandomNonce()
	if err != nil {
		return nil, err
	}
	sh := &cb.SignatureHeader{}
	sh.Creator = creator
	sh.Nonce = nonce
	return sh, nil
}